package com.meeruu.sg.account.core.domain.biz;

import com.meeruu.common.utils.LoggerUtil;
import com.meeruu.sg.account.core.api.constant.AccountGroup;
import com.meeruu.sg.account.core.api.constant.FundsFlowDirect;
import com.meeruu.sg.account.core.api.constant.FundsOprType;
import com.meeruu.sg.account.core.api.dto.request.fund.FrozenRequest;
import com.meeruu.sg.account.core.api.exception.AccountException;
import com.meeruu.sg.account.core.api.exception.ErrorCode;
import com.meeruu.sg.account.core.domain.aspect.RequestSink;
import com.meeruu.sg.account.core.domain.mapper.AccountFrozenFlowMapper;
import com.meeruu.sg.account.core.domain.model.AccountFrozenFlowDO;
import com.meeruu.sg.account.core.domain.vo.UnFrozenDetailRequest;
import com.meeruu.sg.account.core.domain.vo.UnFrozenToOtherDetailRequest;
import com.meeruu.sg.account.core.domain.vo.account.AccountSortVo;
import com.meeruu.sg.account.core.domain.vo.account.BaseAccount;
import com.meeruu.sg.account.core.domain.vo.enums.FrozenTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Map;

/**
 * @author yangqintao
 * @date 2019-05-17
 * @description 冻结、逆向解冻、正向解冻业务
 */
@Slf4j
@Service
public class FundsFrozenBiz {

    @Resource
    private TransactionTemplate transactionTemplate;

    @Autowired
    private FundsBalanceBiz fundsBalanceBiz;

    @Resource
    private AccountFrozenFlowMapper frozenFlowMapper;


    @RequestSink
    public String frozen(FrozenRequest request) {
        return transactionTemplate.execute((TransactionStatus status) -> {
            AccountGroup accountGroup = request.getAccountGroup();
            String accountId = request.getAccountId();
            AccountSortVo accountSortVo = AccountSortVo.getInstance(accountId, accountGroup);

            // 获取账户信息（悲观锁）
            BaseAccount account = fundsBalanceBiz.getAccountBySortForUpdate(accountSortVo)
                    .get(accountSortVo.getAccountId());
            BigDecimal frozenAmount = request.getAmount();

            // 金额校验
            BigDecimal beginBalance = account.getAvailableBalance();
            if (frozenAmount.compareTo(beginBalance) > 0) {
                LoggerUtil.error(log, "accountId:{}金额不足，当前金额:{}, 此次传入需冻结金额:{}",
                        account, beginBalance, frozenAmount);
                throw new AccountException(ErrorCode.BIZ_BALANCE_NOT_ENOUGH);
            }

            BigDecimal frozenBalance = account.getFrozenBalance();
            // 余额-, 冻结金额+
            account.setAvailableBalance(beginBalance.subtract(frozenAmount));
            account.setFrozenBalance(frozenBalance.add(frozenAmount));

            // 冻结记录插入
            String flowId = fundsBalanceBiz.addFrozenFlow(accountId, accountGroup.getCode(),
                    frozenAmount, FundsOprType.FROZEN.getCode(),
                    FundsOprType.FROZEN.getCode(),
                    FundsOprType.FROZEN.getDesc(),
                    request.getApplyDate(), account.getAccountType(),
                    null, FrozenTypeEnum.FROZEN);

            // 余额 -> 冻结金额
            fundsBalanceBiz.updateBalance(account, accountGroup, beginBalance);
            return flowId;
        });

    }

    @RequestSink
    public void unFrozen(UnFrozenDetailRequest detailRequest) {
        transactionTemplate.execute((TransactionStatus status) -> {

            AccountFrozenFlowDO frozenFlow = this.getFrozenFlow(detailRequest.getFrozenId());
            // 冻结记录冻结金额校验
            checkFrozenAmount(frozenFlow, detailRequest.getAmount());

            String accountId = frozenFlow.getAccountId();
            AccountGroup accountGroup = AccountGroup.getByCode(frozenFlow.getAccountGroup());

            Map<String, BaseAccount> result = fundsBalanceBiz.getAccountBySortForUpdate(
                    AccountSortVo.getInstance(accountId, accountGroup));
            BaseAccount account = result.get(accountId);

            BigDecimal nowFrozenBalance = account.getFrozenBalance();
            BigDecimal frozenAmount = detailRequest.getAmount();
            // 校验账户总冻结金额
            if (frozenAmount.compareTo(nowFrozenBalance) > 0) {
                LoggerUtil.error(log, "accountId:{}, 冻结金额不足, 当前冻结金额:{}, 此次传入需解冻金额:{}", account.getAccountId(),
                        nowFrozenBalance, frozenAmount);
                throw new AccountException(ErrorCode.BIZ_FROZEN_BALANCE_NOT_ENOUGH);
            }

            // 解冻记录插入
            String unFrozenId = fundsBalanceBiz.addFrozenFlow(accountId,
                    accountGroup.getCode(), frozenAmount,
                    FundsOprType.UN_FROZEN.getCode(),
                    FundsOprType.UN_FROZEN.getCode(),
                    FundsOprType.UN_FROZEN.getDesc(),
                    detailRequest.getApplyDate(), account.getAccountType(), frozenFlow.getFlowId(), FrozenTypeEnum.UN_FROZEN);

            BigDecimal beginBalance = account.getAvailableBalance();
            // 冻结金额－,余额+
            account.setFrozenBalance(account.getFrozenBalance().subtract(frozenAmount));
            account.setAvailableBalance(beginBalance.add(frozenAmount));

            // 更新冻结金额、余额
            fundsBalanceBiz.updateBalance(account, accountGroup, beginBalance);

            return unFrozenId;
        });

    }

    @RequestSink
    public String unFrozenToOther(UnFrozenToOtherDetailRequest detailRequest) {
        return transactionTemplate.execute((TransactionStatus status) -> {

            AccountFrozenFlowDO frozenFlow = this.getFrozenFlow(detailRequest.getFrozenId());
            // 冻结记录冻结金额校验
            checkFrozenAmount(frozenFlow, detailRequest.getAmount());

            BigDecimal frozenAmount = detailRequest.getAmount();

            // 出账户信息
            String outAccountId = frozenFlow.getAccountId();
            AccountGroup outAccountGroup = AccountGroup.getByCode(frozenFlow.getAccountGroup());

            // 进账户信息
            String inAccountId = detailRequest.getTargetAccountId();
            AccountGroup inAccountGroup = detailRequest.getTargetAccountGroup();

            // 顺序获取账户信息（悲观锁）
            AccountSortVo outAccountSortVo = AccountSortVo.getInstance(outAccountId, outAccountGroup);
            AccountSortVo inAccountSortVo = AccountSortVo.getInstance(inAccountId, inAccountGroup);
            Map<String, BaseAccount> result = fundsBalanceBiz.getAccountBySortForUpdate(outAccountSortVo, inAccountSortVo);

            /* 出账户冻结金额- */
            BaseAccount outAccount = result.get(outAccountId);
            outAccount = updateOutAccountFrozen(frozenAmount, outAccount, outAccountGroup);

            // 进账户余额+
            BaseAccount inAccount = result.get(inAccountId);
            inAccount = updateInAccountBalance(frozenAmount, inAccount, inAccountGroup);

            // 解冻记录插入
            String unFrozenId = fundsBalanceBiz.addFrozenFlow(outAccountId,
                    frozenFlow.getAccountGroup(), frozenAmount,
                    FundsOprType.UN_FROZEN_TO_OTHER.getCode(),
                    FundsOprType.UN_FROZEN_TO_OTHER.getCode(),
                    FundsOprType.UN_FROZEN_TO_OTHER.getDesc(),
                    detailRequest.getApplyDate(), outAccount.getAccountType(),
                    frozenFlow.getFlowId(), FrozenTypeEnum.UN_FROZEN_TO_OTHER);


            FundsOprType flowType = FundsOprType.TRANSFER;
            // 流水
            fundsBalanceBiz.addFundsFlow(outAccountId, inAccountId, frozenAmount,
                    FundsFlowDirect.OUT, flowType, detailRequest.getApplyDate(),
                    outAccount.getAccountType());
            fundsBalanceBiz.addFundsFlow(inAccountId, outAccountId, frozenAmount,
                    FundsFlowDirect.IN, flowType, detailRequest.getApplyDate(),
                    inAccount.getAccountType());

            return unFrozenId;
        });

    }

    /**
     * 获取冻结记录
     *
     * @param frozenId 冻结记录id
     */
    public AccountFrozenFlowDO getFrozenFlow(String frozenId) {
        AccountFrozenFlowDO frozenFlow = frozenFlowMapper.selectByFlowId(frozenId);

        if (null == frozenFlow) {
            LoggerUtil.error(log, "冻结记录不存在,flowId:{}", frozenId);
            throw new AccountException(ErrorCode.BIZ_FROZEN_FLOW_NOT_EXIST);
        }

        return frozenFlow;
    }


    /**
     * 解冻可解冻金额校验（冻结记录的冻结金额 - 当笔记录已解冻金额 > 0）
     *
     * @param frozenFlow 冻结记录
     * @param amount     需解冻金额
     */
    private void checkFrozenAmount(AccountFrozenFlowDO frozenFlow, BigDecimal amount) {
        // 冻结金额
        BigDecimal frozenAmount = frozenFlow.getFlowFrozen();
        // 当笔冻结已解冻总金额
        BigDecimal usedFrozenAmount = frozenFlowMapper.totalAmountByRefFlowId(frozenFlow.getFlowId());

        // 剩余冻结金额
        BigDecimal remainAmount = frozenAmount.subtract(usedFrozenAmount);
        if (remainAmount.compareTo(amount) < 0) {
            LoggerUtil.error(log, "冻结金额不足, frozenId:{}, 当笔冻结记录剩余冻结金额不足:{}, 当笔所需解冻金额为:{}",
                    frozenFlow.getFlowId(), remainAmount, amount);
            throw new AccountException(ErrorCode.BIZ_FROZEN_BALANCE_NOT_ENOUGH);
        }
    }

    /**
     * 进账户余额更新
     *
     * @param frozenAmount   金额
     * @param inAccount      进账户
     * @param inAccountGroup 进账户组别
     */
    private BaseAccount updateInAccountBalance(BigDecimal frozenAmount, BaseAccount inAccount, AccountGroup inAccountGroup) {

        BigDecimal inBeginBalance = inAccount.getAvailableBalance();
        // 进账户余额+
        inAccount.setAvailableBalance(inBeginBalance.add(frozenAmount));
        inAccount.setTotalBalance(inAccount.getTotalBalance().add(frozenAmount));
        // 更新冻结金额、余额
        fundsBalanceBiz.updateBalance(inAccount, inAccountGroup, inBeginBalance);
        return inAccount;
    }

    /**
     * 出账户冻结金额更新
     *
     * @param frozenAmount 冻结金额
     * @param outAccount   出账户
     * @param accountGroup 出账户组别
     */
    private BaseAccount updateOutAccountFrozen(BigDecimal frozenAmount, BaseAccount outAccount, AccountGroup accountGroup) {
        BigDecimal nowFrozenBalance = outAccount.getFrozenBalance();

        // 校验冻结金额
        if (frozenAmount.compareTo(nowFrozenBalance) > 0) {
            LoggerUtil.error(log, "accountId:{}, 冻结金额不足, 当前冻结金额:{}, 此次传入需解冻金额:{}",
                    outAccount.getAccountId(), nowFrozenBalance, frozenAmount);
            throw new AccountException(ErrorCode.BIZ_FROZEN_BALANCE_NOT_ENOUGH);
        }

        BigDecimal outBeginBalance = outAccount.getAvailableBalance();
        // 冻结金额－
        outAccount.setFrozenBalance(outAccount.getFrozenBalance().subtract(frozenAmount));
        outAccount.setTotalBalance(outAccount.getTotalBalance().subtract(frozenAmount));
        fundsBalanceBiz.updateBalance(outAccount, accountGroup, outBeginBalance);

        return outAccount;
    }

}
